21. 多维数据可视化-绘制股票相关性热力图

⭐ 本章学习目标

  • 理解皮尔逊相关系数的数学定义与金融含义
  • 掌握使用 tushare 获取A股数据的方法
  • 学会计算简单收益率对数收益率
  • 掌握 seaborn.heatmap() 绘制相关性热力图
  • 了解层次聚类、网络图等高级可视化方法

⭐ 相关性在金融中的意义

在现代投资组合理论中,资产间的相关性是核心概念:

  • 分散化投资:相关性越低,组合风险越小
  • 系统性风险识别:高相关性意味着暴露于共同因子
  • 配对交易:高相关性股票可能存在价差套利机会
  • 因子模型:通过相关性聚类识别行业与风格因子

⭐ 皮尔逊相关系数的定义

皮尔逊相关系数衡量两个变量线性相关的强度和方向:

\[ \rho_{X,Y} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} = \frac{E[(X-\mu_X)(Y-\mu_Y)]}{\sigma_X \sigma_Y} \]

  • \(\text{Cov}(X,Y)\):协方差,衡量两变量共同变动程度
  • \(\sigma_X, \sigma_Y\):各自的标准差
  • 取值范围\([-1, 1]\)

⭐ 相关系数的直观理解

相关系数范围 含义 投资启示
\([0.7, 1]\) 强正相关 同涨同跌,分散化效果差
\([0.4, 0.7)\) 中度正相关 有一定联动性
\([-0.1, 0.4)\) 弱正相关/无相关 适合组合分散化
\([-0.7, -0.1)\) 中度负相关 天然对冲效果
\([-1, -0.7)\) 强负相关 对冲效果极好

⭐ 投资组合方差与相关性

投资组合方差的完整公式:

\[ \sigma_p^2 = \sum_i \sum_j w_i w_j \sigma_i \sigma_j \rho_{ij} \]

  • \(w_i, w_j\):各资产的权重
  • \(\rho_{ij}\):资产 \(i\)\(j\) 的相关系数
  • 核心启示:相关性越低,组合风险越小——这是马科维茨理论的核心

⭐ 相关系数的局限性

相关系数只能衡量线性关系,存在重要局限:

  • 非线性关系:若 \(Y = X^2\)\(X\) 对称分布,则 \(\rho = 0\),但两者有强关系
  • 异常值敏感:极端观测值会严重扭曲相关系数
  • 不能推因果:相关不等于因果关系
  • 时变性:相关系数会随市场状态变化(牛市/熊市)

⭐ 平台任务:获取数据并绘制热力图

Listing 1
# 注:该代码块存在缩进错误且使用tushare API(非用户token),渲染时无法执行
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import tushare as ts  # 导入tushare模块
import pandas as pd  # 导入Pandas数据分析库
import seaborn as sns  # 导入Seaborn可视化库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文

ts.set_token("1b80258a92e8a547628c8e8477d0c4c590d485d17dfa98eea5a46db7")  # 设置Tushare API访问令牌
pro=ts.pro_api()  # 初始化Tushare Pro API接口
# 获取股票代码和名称的对应关系
stock_list = {'600030.SH': '中信证券', '601318.SH': '中国平安', '600036.SH': '招商银行', '600519.SH': '贵州茅台','601668.SH':'中国建筑'}
# 获取数据
data = {}
for stock_code, stock_name in stock_list.items():  # 循环遍历
        df = pro.daily(ts_code=stock_code, start_date='20230101', end_date='20230301')  # 通过API获取df的行情数据
    data[stock_name] = df['close'].values.tolist()  # 创建列表data[stock_name]
# 转换为DataFrame
df=pd.DataFrame.from_dict(data)
# 绘制热力图
sns.heatmap(df.corr(), annot=True, cmap='coolwarm')
plt.title('股价热力图')  # 设置图表标题
plt.savefig("1.png")  # 保存图形至文件

⭐ 平台代码解读:整体流程

平台代码执行流程 展示从导入库到输出热力图的五个步骤流程图 导入库 设置Token 循环获取 5只股票数据 转换为 DataFrame 计算 corr() 相关系数矩阵 热力图 Step 1 Step 2 Step 3 Step 4 Step 5 tushare/pandas seaborn/matplotlib pro.daily() 循环5只股票 pd.DataFrame .from_dict() df.corr() 皮尔逊系数 sns.heatmap()

⭐ 关键函数解析:pro.daily()df.corr()

pro.daily():获取日线行情

  • ts_code:股票代码(如 '600030.SH'
  • start_date / end_date:起止日期
  • 返回包含 open/high/low/close/vol 等字段的 DataFrame

df.corr():计算相关系数矩阵

  • 默认使用皮尔逊相关系数
  • 输入 DataFrame 的每一列与其他列两两计算
  • 返回对称矩阵,对角线为 1

⭐ 关键函数解析:sns.heatmap()

sns.heatmap(df.corr(), annot=True, cmap='coolwarm') 参数详解:

参数 含义 本例取值
data 输入的二维数据(相关系数矩阵) df.corr()
annot 是否在格子中标注数值 True
cmap 颜色映射方案 'coolwarm'(蓝红渐变)
center 颜色中心值 可设为 0
square 格子是否为正方形 可设为 True

⭐ 常用颜色映射方案对比

名称 效果 适用场景
coolwarm 蓝→白→红 通用,正负对比清晰
RdYlGn 红→黄→绿 金融领域常用
RdBu 红→白→蓝 色盲友好
viridis 紫→绿→黄 感知均匀,适合打印
YlOrRd 黄→橙→红 只有正值时

建议:金融热力图优先使用 'RdYlGn''coolwarm',便于直观区分正负相关。

⭐ 收益率的两种计算方法

在计算相关性前,通常先将价格转换为收益率

简单收益率

\[ R_t = \frac{P_t - P_{t-1}}{P_{t-1}} \]

对数收益率

\[ r_t = \ln\left(\frac{P_t}{P_{t-1}}\right) = \ln(P_t) - \ln(P_{t-1}) \]

  • Python 实现:df.pct_change()np.log(df / df.shift(1))

⭐ 简单收益率 vs 对数收益率

特性 简单收益率 对数收益率
公式 \((P_t - P_{t-1}) / P_{t-1}\) \(\ln(P_t / P_{t-1})\)
可加性 多期不可直接相加 可加:\(r_{1 \to 3} = r_1 + r_2 + r_3\)
对称性 不对称:+10% 和 -10% 不对等 对称
分布 偏态 更接近正态分布
应用 报告收益率 计算波动率、建模

相关性分析建议:使用对数收益率,因为更接近正态分布且具有时间可加性。

⭐ 收益率计算代码演示

Listing 2
# 计算简单收益率
# pct_change(): (当前值 - 上期值) / 上期值
simple_returns = df_prices.pct_change().dropna()

# 计算对数收益率
# ln(P_t / P_{t-1}) = ln(P_t) - ln(P_{t-1})
log_returns = np.log(df_prices / df_prices.shift(1)).dropna()

# 输出收益率预览
print('简单收益率(前5行):')
print(simple_returns.head())
print(f'\n对数收益率(前5行):')
print(log_returns.head())

# 收益率统计对比
print(f'\n简单收益率统计:')
print(simple_returns.describe().round(4))
print(f'\n对数收益率统计:')
print(log_returns.describe().round(4))

⭐ 相关系数矩阵的计算

Listing 3
# 使用对数收益率计算相关性矩阵
# corr() 默认使用皮尔逊相关系数 (method='pearson')
corr_matrix = log_returns.corr()

# 输出相关性矩阵
print('相关性矩阵:')
print(corr_matrix.round(3))
print(f'\n矩阵形状: {corr_matrix.shape}')

# 验证对称性
print(f'\n矩阵对称性检验:')
print(f'是否对称: {np.allclose(corr_matrix, corr_matrix.T)}')
print(f'对角线元素(应全为1): {np.diag(corr_matrix)}')

⭐ 相关系数矩阵的三个重要性质

  1. 对称性\(\rho_{ij} = \rho_{ji}\),矩阵等于其转置
  2. 对角线为 1\(\rho_{ii} = 1\),变量与自身完全正相关
  3. 正定性:协方差矩阵半正定,保证投资组合方差非负

corr() 方法的三种计算方式

参数 方法 特点
'pearson' 皮尔逊(默认) 衡量线性关系
'spearman' 斯皮尔曼秩相关 对异常值稳健
'kendall' 肯德尔 \(\tau\) 相关 适合小样本

⭐ 相关系数的统计显著性检验

检验原假设 \(H_0: \rho = 0\)(两变量无线性相关)

\[ t = \frac{r\sqrt{n-2}}{\sqrt{1-r^2}} \sim t_{n-2} \]

符号 p 值条件 含义
*** \(p < 0.01\) 极显著
** \(p < 0.05\) 显著
* \(p < 0.10\) 边缘显著
ns \(p \geq 0.10\) 不显著

注意:统计显著 \(\neq\) 经济显著,即使 p 值很小,系数很低仍不具实际意义。

⭐ 显著性检验代码演示

Listing 4
from scipy.stats import pearsonr  # 皮尔逊检验

n = len(corr_matrix)
pairs = []
for i in range(n):
    for j in range(i+1, n):
        stock1 = corr_matrix.index[i]
        stock2 = corr_matrix.columns[j]
        corr = corr_matrix.iloc[i, j]
        # pearsonr() 返回 (相关系数, p值)
        _, p_value = pearsonr(
            log_returns[stock1], log_returns[stock2]
        )
        pairs.append({
            '股票1': stock1,
            '股票2': stock2,
            '相关系数': corr,
            'P值': p_value,
            '显著性': '***' if p_value < 0.01
                      else '**' if p_value < 0.05
                      else '*' if p_value < 0.1
                      else 'ns'
        })

df_pairs = pd.DataFrame(pairs).sort_values(
    '相关系数', ascending=False
)
print(df_pairs.to_string(index=False))

⭐ 绘制基础热力图(完整版)

Listing 5
import seaborn as sns

plt.figure(figsize=(10, 8))
sns.heatmap(
    corr_matrix,         # 相关系数矩阵
    annot=True,          # 显示数值
    cmap='RdYlGn',       # 红-黄-绿配色
    center=0,            # 颜色中心值为0
    square=True,         # 正方形格子
    linewidths=0.5,      # 格子边框
    fmt='.3f',           # 保留3位小数
    cbar_kws={'label': '皮尔逊相关系数'},
    vmin=-1, vmax=1      # 固定范围[-1,1]
)
plt.title('股票收益率相关性热力图', fontsize=14, pad=20)
plt.xlabel('股票', fontsize=12)
plt.ylabel('股票', fontsize=12)
plt.tight_layout()
plt.show()

⭐ 热力图参数详解

热力图参数效果 展示sns.heatmap主要参数及其对热力图效果的影响 sns.heatmap() 核心参数 annot=True 在每个格子中显示数值 cmap='RdYlGn' 红黄绿颜色映射方案 center=0 0为颜色中点,正负对比清晰 square=True 保持格子为正方形 fmt='.3f' 数值保留3位小数 linewidths=0.5 格子间边框线宽 vmin=-1, vmax=1 固定颜色范围至理论范围 cbar_kws={...} 自定义颜色条标签等属性

⭐ 进阶:层次聚类热力图

sns.clustermap() 在热力图基础上添加树状图,将相似股票自动聚在一起:

Listing 6
from scipy.cluster.hierarchy import linkage

linkage_matrix = linkage(corr_matrix, method='average')
g = sns.clustermap(
    corr_matrix, annot=True, cmap='RdYlGn',
    center=0, square=True, linewidths=0.5,
    fmt='.3f', figsize=(10, 10),
    row_linkage=linkage_matrix,
    col_linkage=linkage_matrix,
    tree_kws={'linewidths': 1.5}
)
g.fig.suptitle('股票相关性聚类分析', fontsize=14, y=0.98)
plt.show()

⭐ 层次聚类的原理与应用

距离定义:基于相关性转换 \(d = 1 - |\rho|\)

三种链接方法

方法 原理 特点
单链接 类间最小距离 易产生链式效应
完全链接 类间最大距离 类内直径小
平均链接 类间平均距离 最常用,本例采用

金融应用:发现同行业股票、识别风格因子、地域因子分析

⭐ 进阶:网络图可视化

将相关性表示为网络,直观展示股票关联结构:

Listing 7
import networkx as nx

threshold = 0.5  # 只保留|ρ|≥0.5的强相关
G = nx.Graph()
for stock in stocks.values():
    G.add_node(stock)

for i in range(n):
    for j in range(i+1, n):
        corr = corr_matrix.iloc[i, j]
        if abs(corr) >= threshold:
            G.add_edge(
                corr_matrix.index[i],
                corr_matrix.columns[j],
                weight=corr
            )

⭐ 网络图的绘制与解读

Listing 8
plt.figure(figsize=(10, 8))
pos = nx.spring_layout(G, k=0.5, seed=42)
degrees = dict(G.degree())

# 节点:大小与连接数成正比
nx.draw_networkx_nodes(
    G, pos,
    node_size=[v * 300 for v in degrees.values()],
    node_color='steelblue', alpha=0.7
)
# 边:绿色=正相关,红色=负相关
weights = [G[u][v]['weight'] for u, v in G.edges()]
edge_colors = ['red' if w < 0 else 'green' for w in weights]
nx.draw_networkx_edges(
    G, pos,
    width=[abs(w) * 3 for w in weights],
    edge_color=edge_colors, alpha=0.6
)
nx.draw_networkx_labels(G, pos, font_size=10)
plt.title(f'股票相关性网络图(阈值={threshold})')
plt.axis('off')
plt.show()

⭐ 网络图解读要点

网络图解读要点 四个卡片分别解释网络图中节点大小、边颜色、边粗细、节点位置的含义 节点大小 度数(连接数) 越大 = 系统重要性越高 边的颜色 绿色 = 正相关 红色 = 负相关 边的粗细 |ρ| 越大 线条越粗 节点位置 相近 = 相关性高 孤立 = 独立走势

⭐ 滚动窗口相关性分析

相关系数不是恒定的,会随市场状态变化:

Listing 9
stock_a = '中信证券'
stock_b = '中国平安'
window = 20  # 20日滚动窗口(约1个月)

# 计算滚动相关性
rolling_corr = log_returns[stock_a].rolling(
    window
).corr(log_returns[stock_b])

fig, axes = plt.subplots(2, 1, figsize=(14, 10))

# 子图1:滚动相关系数
axes[0].plot(rolling_corr.index, rolling_corr, linewidth=2)
axes[0].axhline(y=0, color='k', linestyle='--')
axes[0].axhline(y=0.5, color='r', linestyle='--', label='阈值0.5')
axes[0].axhline(y=-0.5, color='r', linestyle='--', label='阈值-0.5')
axes[0].set_title(f'{stock_a}{stock_b}的滚动相关性')
axes[0].legend()

# 子图2:归一化价格走势
norm_prices = df_prices[[stock_a, stock_b]] / \
    df_prices[[stock_a, stock_b]].iloc[0] * 100
axes[1].plot(norm_prices[stock_a], label=stock_a)
axes[1].plot(norm_prices[stock_b], label=stock_b)
axes[1].set_title('归一化价格走势(初始=100)')
axes[1].legend()
plt.tight_layout()
plt.show()

⭐ 相关性的时变特征与策略启示

市场状态对相关性的影响

  • 牛市:股票普遍同涨,相关性普遍较高
  • 熊市/危机:恐慌抛售,所有资产相关性趋近 1(“相关性崩塌”)
  • 震荡市:个股分化,相关性降低

策略启示

  • 不能完全依赖历史相关性来构建组合
  • 需要动态监控相关性变化
  • 极端事件时分散化可能失效
  • 2008年金融危机:几乎所有资产同向下跌

⭐ 本章知识总结

本章知识总结 总结本章四大核心模块:理论基础、数据处理、可视化方法、实战应用 本章核心知识框架 理论基础 • 皮尔逊相关系数 • 投资组合方差 • 显著性检验 • 相关 ≠ 因果 数据处理 • tushare 获取数据 • 简单/对数收益率 • df.corr() 计算 • 相关系数矩阵 可视化方法 • sns.heatmap() • sns.clustermap() • 网络图 networkx • 颜色映射方案 实战应用 • 投资组合分散化 • 配对交易策略 • 滚动相关性监控 • 危机期间相关性